
// Copyright (c) 2012, 2013 All Right Reserved, http://hpc.utp.edu.my
// Author: Nordin Zakaria nordinzakaria@gmail.com
// All rights reserved.

#include "Worker.h"
#include "Job.h"
#include <string>
#include <algorithm>
#include <vector>
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
#include "Random.h"
#include "AdjMatrix.h"

#ifndef WORK_SIM
#define WORK_SIM

class WorkSimulation;

typedef void (*WSPreStep)(WorkSimulation &);
typedef void (*WSStep)(WorkSimulation &);
typedef void (*WSPostStep)(WorkSimulation &);
typedef void (*WSInitialize)(WorkSimulation &, unsigned int NumWorkers, string adjfile);

//! this class represents a single simulation process.
class WorkSimulation
    {
	private:
	//! the list of worker agents
	vector<Worker> workers;

	//! the list of job entities
	vector<Job> jobs;

	//! the current number of steps executed for this simulation process
	unsigned int curr_numsteps;

	//! the number of steps required for this simulation process
	unsigned int numsteps;

	//! the communication structure encoded in the form of an adjacency matrix
	AdjMatrix comm;

	//! Prestep method
	WSPreStep prestepM;

	//! Step method
	WSStep stepM;

	//! PostStep method
	WSPostStep poststepM;

	//! Initialize method
	WSInitialize initM;


	public:

	//! initialize the simulation process based on the input file
	/*!
		\param infile	 the name of the input file
	*/
	void init(FILE *infile);

	//! zero out the adjacency matrix
	void zeroComm() {
		comm.zeros();
		}

	//! dump the simulation state into a file
	/*!
		\param filename		the name of the output file
	*/
	void dump(string filename);

	//! dump the simulation state to the standard output
	void dump();

	//! dump the simulation state into a file
	/*!
		\param outfile		the output file
	*/
	void dump(FILE *outfile);

	//! add adjacency matrix from another simulation into this simulation
	/*!
		this will be called by the Boinc master code when gathering results computed by clients.
		\param work		the other simulation object
	*/
	void add(WorkSimulation& work);

	//! perform graph clustering
	/*!
		Graph clustering is performed using some sort of graph partitioning algorithm.
		The result is used to generate a list of simulation objects, 
		each of which is to perform an independent simulation process.
		\param units	 the list of simulation
	*/
	void split(vector<WorkSimulation*>& units);

	//! copy from another adjacency matrix
	/*!
		\param adjcomm		the other adjacency matrix
	*/
	void setAdjMatrix(const AdjMatrix& adjcomm)
		{
		comm.clear();
		unsigned int N = adjcomm.getN();	
		comm.init(N);
		comm = adjcomm;
		}

	//! get the adjacency (communication) matrix for this simulation object
	/*!
		\return the adjacency matrix object
	*/
	AdjMatrix& getComm()
		{
		return comm;
		}

	//! add a worker
	/*!
		\param worker	the worker to be added
	*/
	void addWorker(Worker& worker)
		{
		workers.push_back(worker);
		}

	//! add a list of workers
	/*!
		\param newworkers	the list of workers to be added
	*/
	void addWorkers(const vector<Worker>& newworkers)
		{
		workers.resize(newworkers.size());
		copy(newworkers.begin(), newworkers.end(), workers.begin());
		}

	//! add a job
	/*!
		\param job	the job to be added
	*/
	void addJob(Job& job)
		{
		jobs.push_back(job);
		}

	//! add a list of jobs
	/*!
		\param newjobs	the jobs to be added
	*/
	void addJobs(vector<Job>& newjobs)
		{
		jobs.resize(newjobs.size());
		copy(newjobs.begin(), newjobs.end(), jobs.begin());
		}

	//!  get number of jobs
	/*!
		\return	   the number of jobs
	*/
	int joblistSize()
		{
		return jobs.size();
		}

	//!  get a job
	/*!
		\param i   the index
		\return	   the job
	*/
	Job& getJob(unsigned int i)
		{
		return jobs[i];
		}

	//! dump the current state of the simulation and update Boinc client on the simulation completion state
	/*!
		\param frac		the percentage completion (0 to 1)
	*/
	void do_checkpoint(double frac);

	//! Get worker list 
	/*!
	    \param wlist  the worker list
	*/
	void getWorkerlist(vector<unsigned int> &wlist)
		{
		for (unsigned int i=0; i<workers.size(); i++)
			wlist.push_back(workers[i].getID());
		}

	//! Set the current number of simulation steps 
	/*!
	    \param num  current number of steps
	*/
	void setCurrNumsteps(unsigned int num)
		{
		curr_numsteps = num;
		}

	//! Get the current number of simulation steps 
	/*!
	    \return  current number of steps
	*/
	unsigned int getCurrNumsteps()
		{
		return curr_numsteps;
		}

	//! Set the number of simulation steps 
	/*!
	    \param num  number of steps
	*/
	void setNumsteps(unsigned int num)
		{
		numsteps = num;
		}

	//! Get the number of simulation steps 
	/*!
	    \return  number of steps
	*/
	unsigned int getNumsteps()
		{
		return numsteps;
		}

	public:

	//! start the simulation
	void startSimulation();

	//! set the method to be used for simulation prestep
	/*!
	    \param op   the method
	*/
	WSPreStep set_prestep(WSPreStep op) {
        	return (prestepM=op);
        	}

	//! Get the prestep function to be used for the simulation
	/*!
	    \return  the method
	*/
	WSPreStep get_prestep() const {
        	return prestepM;
        	}

	//! Prestep the simulation
	void prestep() {
        	(*prestepM)(*this);	
        	}

	//! set the method to be used for simulation poststep
	/*!
	    \param op   the method
	*/
	WSPostStep set_poststep(WSPostStep op) {
        	return (poststepM=op);
        	}

	//! Get the step function to be used for the simulation
	/*!
	    \return  the method
	*/
	WSPostStep get_poststep() const {
        	return poststepM;
        	}

	//! Poststep the simulation
	void poststep() {
        	(*poststepM)(*this);	
        	}

	//! set the method to be used for simulation step
	/*!
	    \param op   the method
	*/
	WSStep set_step(WSStep op) {
        	return (stepM=op);
        	}

	//! Get the step function to be used for the simulation
	/*!
	    \return  the method
	*/
	WSStep get_step() const {
        	return stepM;
        	}

	//! Step through the simulation
	void step() {
        	(*stepM)(*this);
        	}

	//! Set the function that initialize a WorkSimulation object
	/*!
		\param op 	the function
	*/
	WSInitialize set_init(WSInitialize op) {
        	return (initM=op);
        	}

	//! Get the function that initialize a WorkSimulation object
	WSInitialize get_init() const {
        	return initM;
        	}

	//! perform initialization of work simulation
	/*!
	    The number of workers to be involved, and the communication matrix has been fixed.
	    \param NumWorkers   number of workers
	    \param adjfile      file containing the adjacency matrix
	*/
	void init(const int NumWorkers, string adjfile) {
        	(*initM)(*this, NumWorkers, adjfile);
        	}

	//! set the method to be used for worker simulation prestepping
	/*!
	    \param op   the method
	*/
	void set_Worker_PreStep(PreStep op) {
		for (unsigned int i=0; i<workers.size(); i++)
			workers[i].set_prestep(op);
        	}

	//! set the method to be used for worker simulation poststepping
	/*!
	    \param op   the method
	*/
	void set_Worker_PostStep(PreStep op) {
		for (unsigned int i=0; i<workers.size(); i++)
			workers[i].set_poststep(op);
        	}

	//! set the method to be used for worker simulation stepping
	/*!
	    \param op   the method
	*/
	void set_Worker_Step(PreStep op) {
		for (unsigned int i=0; i<workers.size(); i++)
			workers[i].set_step(op);
        	}

	};

#endif





